<?php declare(strict_types=1);

namespace App\Core\ModelExtend;


use App\Core\DB;
use App\Core\Model;
use Carbon\Carbon;
use Hyperf\Database\Model\Collection;
use Hyperf\Utils\Str;

/**
 *  自定义的一些功能
 */
trait Custom
{
    /**
     * 根据Id获取一条记录对象
     * @param int $id
     * @param array|null $selectColumn
     * @param bool $lock
     * @param bool $containDeleted
     * @return static|null
     */
    public static function getInfoById(int $id, ?array $selectColumn = null, bool $lock = false, bool $containDeleted = false): ?static
    {
        if (!$id) {
            return null;
        }

        $model = static::where(static::C_ID, $id);

        if ($containDeleted) {
            $model->withTrashed();
        }

        if ($selectColumn) {
            if ($lock) {
                return $model->lockForUpdate()->first($selectColumn);
            }

            return $model->first($selectColumn);
        }

        if ($lock) {
            return $model->lockForUpdate()->first();
        }

        return $model->first();
    }

    /**
     * 获取一条记录对象
     * @param array $params
     * @param array|null $selectColumn
     * @param bool $lock
     * @param bool $containDeleted
     * @return static|null
     */
    public static function getInfoByParam(array $params, ?array $selectColumn = null, bool $lock = false, bool $containDeleted = false): ?static
    {
        $db = static::query();
        foreach ($params as $key => $val) {
            if (is_array($val)) {
                $db->whereIn($key, $val);
                continue;
            }
            $db->where($key, $val);
        }

        if ($containDeleted) {
            $db->withTrashed();
        }

        if ($lock) {
            $db->lockForUpdate();
        }

        if ($selectColumn) {
            return $db->first($selectColumn);
        }

        return $db->first();
    }

    /**
     * 获取指定id的名字
     * @param array $ids
     * @param string $nameColumn
     * @return array
     */
    public static function getNamesByIds(array $ids, string $nameColumn = ''): array
    {
        $data = [];
        if (!$ids) {
            return $data;
        }

        if (!$nameColumn) {
            $nameColumn = static::C_NAME;
        }

        $tA = static::whereIn(static::C_ID, $ids)->orderByDesc(static::C_ID)
            ->orderBy(static::C_ID)->get([$nameColumn, static::C_ID]);
        if ($tA) {
            foreach ($tA as $item) {
                $data[$item->{static::C_ID}] = $item->{$nameColumn};
            }

            foreach ($ids as $id) {
                if (!isset($data[$id])) {
                    $data[$id] = '';
                }
            }
        }

        return $data;
    }

    /**
     * 更加id获取名字
     * @param int $id
     * @param string|null $nameColumn
     * @return string
     */
    public static function getNameById(int $id, ?string $nameColumn = null): string
    {
        if (!$nameColumn) {
            $nameColumn = static::C_NAME;
        }

        return (string)static::where(static::C_ID, $id)->value($nameColumn);
    }

    /**
     * 查询记录是否存在
     * @param string $column
     * @param mixed $value
     * @return bool
     */
    public static function existByColumn(string $column, mixed $value): bool
    {
        if (is_array($value)) {
            return static::whereIn($column, $value)->exists();
        }
        return static::where($column, $value)->exists();
    }

    /**
     * 查询记录是否存在
     * @param array $columns
     * @param array $notColumns
     * @param bool $containDeleted
     * @return bool
     */
    public static function existByColumns(array $columns, array $notColumns = [], bool $containDeleted = false): bool
    {
        $db = null;
        foreach ($columns as $column => $value) {
            if (!$db) {
                $db = static::where($column, $value);
                continue;
            }
            $db->where($column, $value);
        }

        if ($notColumns && !empty($notColumns[1])) {
            $db->where($notColumns[0], '<>', $notColumns[1]);
        }

        if ($containDeleted) {
            $db->withTrashed();
        }

        return $db->exists();
    }

    /**
     * 根据id获取数量
     * @param array $ids
     * @return int
     */
    public static function getNumberByIds(array $ids): int
    {
        if (!$ids) {
            return 0;
        }

        return static::whereIn(static::C_ID, $ids)->count();
    }

    /**
     * 获取指定的id集合
     * @param array $ids
     * @param array $columns
     * @param bool $idKey
     * @param bool $lock
     * @return static[]|Collection
     */
    public static function getListByIds(array $ids, array $columns = [], bool $idKey = false, bool $lock = false): array|Collection
    {
        $data = [];
        if (!$ids) {
            return $data;
        }

        $db = static::whereIn(static::C_ID, $ids);
        if ($lock) {
            $db->lockForUpdate();
        }

        if (!$columns) {
            $tA = $db->get();
        } else {
            $tA = $db->get($columns);
        }

        if ($tA->isEmpty()) {
            return [];
        }

        if ($idKey) {
            foreach ($tA as $item) {
                $data[$item->{static::C_ID}] = $item;
            }
        } else {
            $data = $tA;
        }

        return $data;
    }

    /**
     * 获取指定的列
     * @param array $params
     * @param string $columnName
     * @return static[]|Collection
     */
    public static function getColumnListByParams(array $params, string $columnName): array|Collection
    {
        $data = [];
        $db = null;
        foreach ($params as $_column => $item) {
            if (!$db) {
                if (is_array($item)) {
                    $db = static::whereIn($_column, $item);
                    continue;
                }
                $db = static::where($_column, $item);
            }

            if (is_array($item)) {
                $db->whereIn($_column, $item);
                continue;
            }

            $db->where($_column, $item);
        }

        $tA = $db->get([$columnName]);
        if ($tA->isEmpty()) {
            return $data;
        }

        foreach ($tA as $item) {
            $data[] = $item->{$columnName};
        }

        return $data;
    }

    /**
     * 获取列表
     * @param array $params
     * @param array $column
     * @param array $orders
     * @param array $width
     * @param bool $containDeleted
     * @param int|null $limit
     * @param bool $lock
     * @return array|Collection
     */
    public static function getList(array $params, array $column = [], array $orders = [],
                                   array $width = [], bool $containDeleted = false, ?int $limit = null, bool $lock = false): array|Collection
    {
        $db = null;
        if ($column) {
            $db = static::select($column);
        } else {
            $db = static::select();
        }

        foreach ($params as $_column => $item) {
            if (is_array($item)) {
                if (!empty($item[0]) && $item[0] == 'like') {
                    $db->where($_column, 'like', $item[1]);
                    continue;
                }

                if (!empty($item[0]) && $item[0] == '<>') {
                    $db->where($_column, '<>', $item[1]);
                    continue;
                }

                if (!empty($item[0]) && $item[0] == '>') {
                    $db->where($_column, '>', $item[1]);
                    continue;
                }

                $db->whereIn($_column, $item);
                continue;
            }
            $db->where($_column, $item);
        }

        if ($orders) {
            foreach ($orders as $_column => $_orderType) {
                $db->orderBy($_column, $_orderType);
            }
        }

        foreach ($width as $item) {
            $db->with($item);
        }

        if ($containDeleted) {
            $db->withTrashed();
        }

        if ($limit) {
            $db->take($limit);
        }

        if ($lock) {
            $db->lockForUpdate();
        }

        if ($column) {
            $tO = $db->get($column);
        } else {
            $tO = $db->get();
        }

        if ($tO->isEmpty()) {
            return [];
        }

        return $tO;
    }

    /**
     * 获取所有的数据
     * @param array|null $columns
     * @param array $params
     * @param array|null $orders
     * @return static[]|Collection
     */
    public static function getAll(?array $columns = null, array $params = [], ?array $orders = null): array|Collection
    {
        $data = [];
        $db = null;

        if ($orders) {
            foreach ($orders as $_column => $_orderType) {
                if (!$db) {
                    $db = static::orderBy($_column, $_orderType);
                    continue;
                }

                $db->orderBy($_column, $_orderType);
            }
        } else {
            $db = static::orderBy(static::C_ID);
        }

        if ($params) {
            foreach ($params as $_column => $item) {
                if (is_array($item)) {
                    if (in_array($item[0], ['>=', '<=', '<', '>'])) {
                        $db->where($_column, $item[0], $item[1]);
                    } else {
                        $db->whereIn($_column, array_unique($item));
                    }
                } else {
                    $db->where($_column, $item);
                }
            }
        }

        if ($columns) {
            $data = $db->get($columns);
        } else {
            $data = $db->get();
        }

        if ($data->isEmpty()) {
            return [];
        }

        return $data;
    }

    /**
     * 获取所有有引用的配置字段
     * @return array
     */
    public static function getRelationColumn(): array
    {
        $config = [];
        foreach (static::COLUMN as $column => $item) {
            if (!isset($item['relation'])) {
                continue;
            }
            $config[$column] = $item;
        }

        return $config;
    }

    /**
     * 移除并获取多对多的字段
     * @return array
     */
    protected function getAndremoveManyToMantyRelationColumms(): array
    {
        $relation = [];
        $columns = static::getRelationColumn();
        $attributes = $this->getAttributes();
        foreach ($columns as $key => $column) {
            if (isset($column['relation']) && $column['relation']['type'] == 'many_to_many') {
                $column['relation']['value'] = isset($attributes[$key]) ? $this->{$key} : null;
                $relation[$key] = $column['relation'];
                unset($this->{$key});
            }
        }

        unset($attributes, $columns);
        return $relation;
    }

    /**
     * 执行数组中的回调函数
     * @param Array $callbacks
     * @param string $name
     * @param array $param
     * @return bool
     */
    protected static function executionCallbackByArray(array $callbacks, string $name, array $param = []): bool
    {
        foreach ($callbacks as $key => $item) {
            if ($key != $name) {
                continue;
            }

            $res = $item(...$param);
            if ($res) {
                return $res;
            }

            return true;
        }

        return true;
    }

    /**
     * 获取所有有删除引用的配置
     * @return array
     */
    public static function getRelationDeleteColumn(): array
    {
        $config = [];
        foreach (static::COLUMN as $column => $item) {
            if (!isset($item['relation']) || !isset($item['relation']['delete_type'])) {
                continue;
            }

            $config[] = $item;
        }

        return $config;
    }

    /**
     * 获取指定字段的属性
     * @param $clumns
     * @return array
     */
    public function getAttributesInClumns(array $clumns): array
    {
        $_attributes = [];
        $attributes = $this->getAttributes();

        if (!$attributes) {
            return [];
        }

        foreach ($attributes as $attribute => $value) {
            if (!in_array($attribute, $clumns)) {
                continue;
            }

            $_attributes[Str::camel($attribute)] = $this->{$attribute};
        }

        return $_attributes;
    }

    /**
     * @param array $appointColumns
     * @param array $requiredColumns
     * @param bool $initentitie
     * @param array|null $data
     * @return static|null
     * @throws \Exception
     */
    public static function validateColumn(array $appointColumns = [], array $requiredColumns = [],
                                          bool  $initentitie = false, ?array $data = null): ?static
    {
        $_entity = null;
        $rules = [];
        $allAppointColumnss = array_merge($appointColumns, $requiredColumns);
        $_data = [];
        foreach ($allAppointColumnss as $column => $item) {
            if (is_int($column)) {
                $column = $item;
            }

            if (!isset(static::COLUMN[$column])) {
                throw new \Exception('指定的' . static::class . '的' . $column . '不存在');
            }

            $_rule = static::COLUMN[$column]['type'];
            if ($requiredColumns && in_array($column, $requiredColumns)) {
                $_rule = 'required|' . $_rule;
            } else {
                $_rule = 'nullable|' . $_rule;
            }

            $rules[] = [static::COLUMN[$column]['name'], $column, $_rule];
            if ($initentitie) {
                if (!$_entity) {
                    $_entity = new static();
                }
                $value = $data[$column] ?? I($column);
                //等于 null 不赋值
                if ($value !== null) {
                    $_entity->{$column} = $data[$column] ?? I($column);
                }
            }

            $_data[$column] = $data[$column] ?? I($column);
        }

        validate($rules, $_data);
        return $_entity;
    }

    /**
     * 自定义保存
     * @param self $obj
     * @return bool
     */
    public function customSave($param = []): bool
    {
        //指定唯一字段
        $uniqueColumn = $param['uniqueColumn'] ?? null;

        //数据监测范围，主要用于修改和数据检查的时候
        $checkScope = $param['checkScope'] ?? null;

        //主键值
        $updateKey = $param['updateKey'] ?? 'id';

        //指定是编辑还是修改
        $action = $param['action'] ?? null;

        //是否开启执行事务
        $transaction = $param['transaction'] ?? true;

        //修改数据的时候加上排它锁
        $updateReadLocked = $param['updateReadLocked'] ?? false;

        //修改数据的时候加上共享锁
        $updateReadShareLocked = $param['updateReadShareLocked'] ?? false;

        //执行数据验证
        static::executionCallbackByArray($param, 'validateCallback', [$this]);

        if ($transaction) {
            DB::beginTransaction();
        }

        $updated = true;
        $updateBeforeModel = null;

        /*
         * 判断当前是修改还是新增
         */
        if (!$this->{$updateKey}) {
            $updated = false;
        }

        /*
         * 数据字段唯一验证
         */
        if ($this->executionCallbackByArray($param, 'existCallback') && $uniqueColumn) {
            $tA = [];
            if (!is_array($uniqueColumn)) {
                $tA[] = $uniqueColumn;
            } else {
                $tA = $uniqueColumn;
            }

            foreach ($tA as $item) {
                if (!$this->{$item}) {
                    continue;
                }

                $query = static::class::where($item, $this->{$item});
                if ($updated) {
                    $query->where($updateKey, '<>', $this->{$updateKey});
                }

                if ($checkScope) {
                    $query->where($checkScope);
                }

                $existed = $query->exists();
                if ($existed) {
                    responseError(static::getColumnName($item) . '已存在');
                }
            }
        }

        /*
         * 组装剩余数据
         */
        if ($updated) {
            $query = static::where($updateKey, $this->{$updateKey});
            if ($checkScope) {
                $query->where($checkScope);
            }

            if($updateReadLocked) {
                $query->lockForUpdate();
            }

            if($updateReadShareLocked) {
                $query->sharedLock();
            }

            $_mdoel = $query->first();
            if (!$_mdoel) {
                responseError('未找到相关修改信息');
            }

            $updateBeforeModel = clone $_mdoel;
            static::executionCallbackByArray($param, 'updateBeforeOriginalModelCallback', [$updateBeforeModel]);
            $_mdoel->updated_at = Carbon::now();

            $_attributes = $_mdoel->getAttributes();
            $attributes = $this->getAttributes();

            foreach ($_attributes as $key => $val) {
                //没有在存在修改的字段或者修改的字段为空标识不作修改
                if (isset($attributes[$key]) && $attributes[$key] !== null) {
                    continue;
                }

                $temp = $_mdoel->{$key};
                $this->{$key} = is_array($temp) ? $temp : $val; //如果不是json格式的都用原始数据
            }

            $this->original = $_attributes; //修改前的原始数据
            $this->exists = true; //标识数据库已经存在
            unset($_mdoel);
            unset($attributes, $_attributes);
        } else {
            $this->updatedAt = Carbon::now();
            $this->createdAt = Carbon::now();
        }

        //保存前修改对象参数
        static::executionCallbackByArray($param, 'saveBeforeSetParamCallback', [$this, $updateBeforeModel]);

        /*
         * 保存或者检查关联关系
         */
        static::saveOrCheckRelationColumn($this, $updateBeforeModel);

        /*
         * 保存文件
         */
        static::saveFile($this);

        /*
         * 执行保存成功后的回调
         */
        static::executionCallbackByArray($param, 'saveCallback', [$this, $updateBeforeModel]);

        if ($transaction) {
            DB::commit();
        }

        return true;
    }

    /**
     * 保存或者检查关联字段
     * @param object $model
     * @return bool
     * @throws \App\Exception\HttpResponseException
     */
    protected static function saveOrCheckRelationColumn(object $model, ?object $updateBeforeModel = null): bool
    {
        $manyToManyColumns = $model->getAndremoveManyToMantyRelationColumms(); //移除并获取多对多的关系字段
        $thisSaved = false; //标记当前对象是否保存过
        $columns = static::getRelationColumn();
        foreach ($columns as $_key => $column) {
            if (in_array($column['relation']['type'], ['one_to_many'])) {
                continue;
            }

            if ($column['relation']['type'] == 'many_to_many' && (!isset($manyToManyColumns[$_key]) || $manyToManyColumns[$_key]['value'] === null)) {
                continue;
            } elseif ($column['relation']['type'] != 'many_to_many') {
                if ($model->{$_key} == null) {
                    continue;
                }

                //如果和原始修改数据相等，就不需要验证了
                if ($updateBeforeModel && $updateBeforeModel->{$_key} == $model->{$_key}) {
                    continue;
                }
            }

            $config = $column['relation'];

            //查询依赖数据
            $query = null;
            $_values = null; //携带参数值
            if ($config['type'] == 'many_to_many') {
                $_values = $manyToManyColumns[$_key]['value'];
                $query = $config['config'][0]::whereIn($config['config'][0]::C_ID, $_values);
            } else {
                $_values = $model->{$config['config'][1]};
                $query = $config['config'][0]::where($config['config'][2], $_values);
            }

            if (!empty($config['where']) && $_values) {
                $query->where($config['where']);
            }

            if ($config['type'] == 'many_to_many') {
                if ($_values) {
                    $number = $query->count($config['config'][0]::C_ID);
                    if (count($_values) != $number) {
                        responseError($column['name'] . '参数错误');
                    }
                }

                //保存获取id
                if (!$thisSaved) {
                    if (!$model->save()) {
                        responseError('保存失败');
                    }
                    $thisSaved = true;
                }
                //查询数据库已经存在的记录
                $model->manyToManyChange[$_key] = $model->$_key()->sync($_values ?? []);
            } elseif ($_values) {
                //检查依赖数据
                $exsit = $query->exists();
                if (!$exsit) {
                    responseError($column['name'] . '不存在或已删除');
                }
            }
        }

        if (!$thisSaved) {
            if (!$model->save()) {
                responseError('保存失败');
            }
        }

        return true;
    }

    /**
     * 批量更新数据
     * @param array $multipleData
     * @return int
     */
    public static function updateBatch(array $multipleData): int
    {
        $tableName = static::TABLE; // 表名
        $firstRow = current($multipleData);

        $updateColumn = array_keys($firstRow);
        // 默认以id为条件更新，如果没有ID则以第一个字段为条件
        $referenceColumn = isset($firstRow['id']) ? 'id' : current($updateColumn);
        unset($updateColumn[0]);

        // 拼接sql语句
        $updateSql = "UPDATE " . $tableName . " SET ";
        $sets = [];
        $bindings = [];
        foreach ($updateColumn as $uColumn) {
            $setSql = "`" . $uColumn . "` = CASE ";
            foreach ($multipleData as $data) {
                $setSql .= "WHEN `" . $referenceColumn . "` = ? THEN ? ";
                $bindings[] = $data[$referenceColumn];
                $bindings[] = $data[$uColumn];
            }
            $setSql .= "ELSE `" . $uColumn . "` END ";
            $sets[] = $setSql;
        }
        $updateSql .= implode(', ', $sets);
        $whereIn = collect($multipleData)->pluck($referenceColumn)->values()->all();
        $bindings = array_merge($bindings, $whereIn);
        $whereIn = rtrim(str_repeat('?,', count($whereIn)), ',');
        $updateSql = rtrim($updateSql, ", ") . " WHERE `" . $referenceColumn . "` IN (" . $whereIn . ")";
        // 传入预处理sql语句和对应绑定数据
        return DB::update($updateSql, $bindings);
    }

    /**
     * 自定义删除
     * 该方法会物理删除所有的数据，暂时不支持逻辑删除
     * @return bool
     */
    public function customDelete()
    {
        DB::beginTransaction();
        $columns = static::getRelationDeleteColumn();
        $hasDeleted = defined('static::DELETED_AT') ?? NULL;
        foreach ($columns as $column) {
            $config = $column['relation'];
            $query = null;
            if ($config['type'] == 'many_to_many') {
                $query = $config['config'][1]::where($config['config'][2], $this->{$config['config'][1]::C_ID});
            } elseif ($config['type'] == 'belongs_to') {
                $query = $config['config'][0]::where($config['config'][2], $this->{$config['config'][1]});
            } else {
                $query = $config['config'][0]::where($config['config'][1], $this->{$config['config'][2]});
            }

            if (!empty($config['where'])) {
                $query->where($config['where']);
            }

            if ($config['delete_type'] == static::DELETE_TYPE_DELETE_BATCH) {
                $query->delete();
            } elseif ($config['delete_type'] == static::DELETE_TYPE_DELETE) {
                $query->orderBy('id')->chunk(50, function ($data) {
                    foreach ($data as $item) {
                        $item->customDelete();
                    }
                });
            } elseif ($config['delete_type'] == static::DELETE_TYPE_TIPS) {
                $existed = $query->exists();
                if ($existed) {
                    responseError($column['name'] . '引用中不能删除');
                }
            }
        }
        $this->delete();
        DB::commit();
        return true;
    }

    /**
     * 修改
     * @param array $where
     * @param array $data
     * @return bool
     */
    public static function updateByParams(array $where, array $data): int
    {
        $db = static::query();
        foreach ($where as $_column => $item) {
            if (is_array($item)) {
                $db->whereIn($_column, $item);
                continue;
            }
            $db->where($_column, $item);
        }

        return $db->update($data);
    }

    /**
     * 删除
     * @param array $params
     * @return bool
     */
    public static function deleteByParams(array $params): bool
    {
        $db = static::query();
        foreach ($params as $_column => $item) {
            if (is_array($item)) {
                $db->whereIn($_column, $item);
                continue;
            }
            $db->where($_column, $item);
        }

        return (bool)$db->delete();
    }
}
